import requests
import json
import time
import os
import sys
import subprocess
from datetime import datetime
from PyQt5.QtWidgets import (
    QApplication,
    QMainWindow,
    QWidget,
    QVBoxLayout,
    QHBoxLayout,
    QLabel,
    QLineEdit,
    QPushButton,
    QTextEdit,
    QGroupBox,
    QCheckBox,
    QSpinBox,
    QTabWidget,
    QTableWidget,
    QTableWidgetItem,
    QMessageBox,
    QFileDialog,
    QSplitter,
    QFormLayout,
    QProgressBar,
    QStatusBar,
)
from PyQt5.QtCore import QTimer, pyqtSignal, QObject, QThread, QMutex, Qt
from PyQt5.QtGui import QFont, QTextCursor, QIcon
import logging
from logging.handlers import QueueHandler
import queue


class LogHandler(QueueHandler):
    """自定义日志处理器，将日志发送到队列"""

    def __init__(self, log_queue):
        super().__init__(log_queue)
        self.log_queue = log_queue


class AlertWindow(QWidget):
    """告警弹窗"""

    def __init__(self, alert_info):
        super().__init__()
        self.alert_info = alert_info
        self.init_ui()
        self.setup_timer()

    def init_ui(self):
        self.setWindowTitle("🚨 涨停股票告警")
        # 设置窗口置顶
        self.setWindowFlags(Qt.WindowStaysOnTopHint | Qt.Window)
        # 先设置初始大小，不设置位置
        self.resize(400, 300)

        self.setStyleSheet(
            """
            QWidget { background-color: #f0f0f0; }
            QLabel { color: #333; padding: 5px; }
            .title { font-size: 16px; font-weight: bold; color: #d32f2f; }
            .info { font-size: 12px; }
        """
        )

        layout = QVBoxLayout()
        layout.setSpacing(5)  # 减少控件间距
        layout.setContentsMargins(10, 10, 10, 10)  # 减少边距

        # 判断是单个股票还是多个股票
        if isinstance(self.alert_info, dict) and "plate_reason" in self.alert_info:
            # 新格式：多个股票的板块告警
            plate_reason = self.alert_info["plate_reason"]
            stocks = self.alert_info["stocks"]
            count = self.alert_info["count"]

            # 获取板块名称作为显示的涨停原因
            plate_name = ""
            if stocks:
                surge_reason_obj = stocks[0].get("surge_reason")
                if surge_reason_obj and isinstance(surge_reason_obj, dict):
                    related_plates = surge_reason_obj.get("related_plates", [])
                    if related_plates:
                        plate_name = related_plates[0].get("plate_name", "")

            # 板块原因（显示板块名称，而不是详细原因）
            reason_label = QLabel(f"板块原因: {plate_name}")
            reason_label.setStyleSheet("font-size: 14px; font-weight: bold; color: #1976d2;")

            # 股票列表（增大字体）
            stocks_text = "涨停股票:\n"
            for i, stock in enumerate(stocks, 1):
                # 获取首次涨停时间
                first_limit_up_time = "未知"
                if stock.get("first_limit_up"):
                    try:
                        # 转换时间戳为可读格式
                        timestamp = stock["first_limit_up"]
                        first_limit_up_time = datetime.fromtimestamp(timestamp).strftime("%H:%M:%S")
                    except:
                        first_limit_up_time = "未知"

                # 显示股票名称、连板天数和首次涨停时间
                stocks_text += f"{i}. {stock['stock_chi_name']} {stock['limit_up_days']}板 {first_limit_up_time}\n"

            stocks_label = QLabel(stocks_text.strip())  # 去掉最后的换行符
            stocks_label.setStyleSheet("font-size: 14px; font-weight: bold;")  # 增大字体并加粗

            layout.addWidget(reason_label)
            layout.addWidget(stocks_label)

        else:
            # 兼容旧格式：单个股票告警
            stock = self.alert_info
            surge_reason_obj = stock.get('surge_reason')
            plate_name = '暂无'  # 改为获取plate_name
            if surge_reason_obj and isinstance(surge_reason_obj, dict):
                related_plates = surge_reason_obj.get('related_plates', [])
                if related_plates:
                    plate_name = related_plates[0].get('plate_name', '暂无')  # 改为plate_name

            info_text = f"""股票代码: {stock['symbol']}
    股票名称: {stock['stock_chi_name']}
    当前价格: {stock['price']}
    涨幅: {stock['change_percent']:.2%}
    连板天数: {stock['limit_up_days']}
    板块原因: {plate_name}"""

            info_label = QLabel(info_text)
            info_label.setStyleSheet("font-size: 14px; font-weight: bold;")  # 增大字体并加粗

            layout.addWidget(info_label)

        # 告警时间
        time_label = QLabel(f"告警时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
        time_label.setStyleSheet("font-size: 10px; color: #666;")
        layout.addWidget(time_label)

        self.setLayout(layout)

        # 调整窗口大小以适应内容
        self.adjustSize()
        self.setFixedSize(self.size())  # 固定窗口大小

        # 设置窗口位置到右下角
        self.move_to_bottom_right()

    def move_to_bottom_right(self):
        """将窗口移动到屏幕右下角（动态调整）"""
        from PyQt5.QtWidgets import QApplication

        # 获取屏幕尺寸
        screen = QApplication.desktop().screenGeometry()

        # 获取窗口尺寸
        window_size = self.size()

        # 设置固定的距离边缘的像素值
        offset_from_right = 30  # 距离右边缘30像素
        offset_from_bottom = 150  # 距离底部150像素（往上抬）

        # 计算位置
        x = screen.width() - window_size.width() - offset_from_right
        y = screen.height() - window_size.height() - offset_from_bottom

        # 确保窗口不会超出屏幕范围
        if x < 0:
            x = 0
        if y < 0:
            y = 0

        # 移动窗口到计算出的位置
        self.move(x, y)

    def show(self):
        """显示窗口"""
        super().show()
        self.raise_()
        self.activateWindow()

    def setup_timer(self):
        """设置180秒自动关闭"""
        self.timer = QTimer()
        self.timer.timeout.connect(self.close)
        self.timer.start(180000)  # 180秒

    def closeEvent(self, event):
        """关闭事件"""
        if hasattr(self, "timer"):
            self.timer.stop()
        event.accept()


class StockMonitorWorker(QObject):
    """股票监控工作线程"""

    alert_signal = pyqtSignal(dict)
    log_signal = pyqtSignal(str)
    stats_signal = pyqtSignal(dict)

    def __init__(self, config):
        super().__init__()
        self.config = config
        self.running = False
        self.mutex = QMutex()
        self.today = datetime.now().strftime("%Y-%m-%d")
        self.known_stocks = set()
        self.reason_alerts = {}  # 记录已告警的板块原因
        self.triggered_keywords = {}  # 记录每个关键词已不会重复告警
        # 使用三元运算符将关键词转为集合，避免 None 值
        self.surge_keywords = {config.get("keyword1", ""), config.get("keyword2", ""), config.get("keyword3", "")}
        self.surge_keywords.discard("")  # 移除空字符串
        self.setup_api()
        self.load_data()

    def setup_api(self):
        """设置API"""
        self.headers = {
            "Host": "flash-api.xuangubao.com.cn",
            "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36",
            "accept": "application/json, text/plain, */*",
            "origin": "https://xuangutong.com.cn",
            "referer": "https://xuangutong.com.cn/dingpan",
        }
        self.url = "https://flash-api.xuangubao.com.cn/api/pool/detail"
        self.params = {"pool_name": "limit_up"}

    def update_config(self, config):
        """更新配置"""
        self.mutex.lock()
        self.config = config
        self.mutex.unlock()

    def analyze_plate_reasons(self, current_data):
        """分析板块原因统计"""
        if not current_data:
            return {}, {}

        reason_count = {}
        reason_stocks = {}

        for stock in current_data:
            surge_reason_obj = stock.get("surge_reason")
            if surge_reason_obj is None:
                continue
            if surge_reason_obj and isinstance(surge_reason_obj, dict):
                related_plates = surge_reason_obj.get("related_plates", [])
                if related_plates:
                    # 取第一个板块的原因
                    plate_reason = related_plates[0].get("plate_reason", "")
                    if plate_reason:
                        if plate_reason not in reason_count:
                            reason_count[plate_reason] = 0
                            reason_stocks[plate_reason] = []
                        reason_count[plate_reason] += 1
                        reason_stocks[plate_reason].append(stock)

        return reason_count, reason_stocks

    def should_alert_by_reason(self, reason, count):
        """基于板块原因判断是否应该告警"""
        # 板块原因数量少于2，不告警
        if count < 2:
            return False

        # 板块原因数量 >= 2，且该原因从未告警过，则告警
        if count >= 2 and reason not in self.reason_alerts:
            return True

        # 其他情况不告警
        return False

    def get_alert_stocks(self, current_data):
        """获取需要告警的股票"""
        if not current_data:
            return []

        reason_count, reason_stocks = self.analyze_plate_reasons(current_data)
        alert_stocks = []

        for reason, count in reason_count.items():
            if self.should_alert_by_reason(reason, count):
                # 将该板块原因下的所有股票加入告警列表
                alert_stocks.extend(reason_stocks[reason])
                self.log_signal.emit(f"板块原因告警: {reason} (共{count}只股票)")

        return alert_stocks

    def get_keyword_from_reason(self, plate_reason):
        """从板块原因中获取与配置关键词匹配的关键词"""
        for keyword in self.surge_keywords:
            if keyword in plate_reason:
                return keyword
        return None
    def load_data(self):
        """加载数据"""
        self.known_stocks = set()
        self.stock_alerts = {}
        self.triggered_keywords = {}  # 重新初始化每次都要加载

        alert_file = f"alerts_{self.today}.json"
        if os.path.exists(alert_file):
            try:
                with open(alert_file, "r", encoding="utf-8") as f:
                    alerts = json.load(f)
                    for alert in alerts:
                        symbol = alert["symbol"]
                        limit_days = alert["limit_up_days"]
                        plate_reason = alert.get("plate_reason", "")
                        keyword = self.get_keyword_from_reason(plate_reason)  # 从原因统计中获取关键词

                        # 更新记录的关键词
                        if keyword:
                            self.triggered_keywords[keyword] = True  # 记录已告警的关键词

                        # 原有逻辑保持不变
                        if symbol not in self.stock_alerts:
                            self.stock_alerts[symbol] = []
                        if limit_days not in self.stock_alerts[symbol]:
                            self.stock_alerts[symbol].append(limit_days)

                        # 记录已告警的板块原因
                        if plate_reason and plate_reason not in self.reason_alerts:
                            self.reason_alerts[plate_reason] = True

                self.log_signal.emit(
                    f"加载历史告警记录: {len(self.stock_alerts)} 只股票, {len(self.reason_alerts)} 个板块原因"
                )
            except Exception as e:
                self.log_signal.emit(f"加载告警历史失败: {e}")

    def check_date_change(self):
        """检查日期变化"""
        current_date = datetime.now().strftime("%Y-%m-%d")
        if current_date != self.today:
            self.today = current_date
            self.load_data()
            self.log_signal.emit(f"日期变更到: {self.today}")

    def fetch_data(self):
        """获取数据"""
        self.mutex.lock()
        config = self.config.copy()
        self.mutex.unlock()

        proxies = None
        if config.get("use_proxy", False):
            proxy_url = f"http://{config.get('proxy_host', '127.0.0.1')}:{config.get('proxy_port', 8442)}"
            proxies = {"http": proxy_url, "https": proxy_url}

        for attempt in range(config.get("max_retries", 5)):
            try:
                response = requests.get(
                    self.url,
                    headers=self.headers,
                    params=self.params,
                    timeout=config.get("timeout", 10),
                    proxies=proxies,
                )

                if response.status_code == 200:
                    data = response.json()
                    if not data:  # 确保data不是None
                        self.log_signal.emit("API返回空数据")
                        return []

                    if data.get("code") == 20000:
                        self.save_raw_data(data)

                        if 'data' in data and data['data'] is not None:
                            return data['data']
                        else:
                            self.log_signal.emit("获取的数据格式无效")
                            return []
                    else:
                        self.log_signal.emit(f"API返回错误: {data.get('message', 'Unknown error')}")
                else:
                    self.log_signal.emit(f"HTTP错误: {response.status_code}")

            except requests.exceptions.RequestException as e:
                self.log_signal.emit(
                    f"请求失败 (尝试 {attempt + 1}/{config.get('max_retries', 5)}): {e}"
                )

            if attempt < config.get("max_retries", 5) - 1:
                time.sleep(2)

        return None

    def save_raw_data(self, data):
        """保存原始JSON数据"""
        raw_file = f"raw_data_{self.today}.json"
        timestamp = datetime.now().strftime("%H:%M:%S")
        raw_data = {"timestamp": timestamp, "data": data}

        raw_list = []
        if os.path.exists(raw_file):
            try:
                with open(raw_file, "r", encoding="utf-8") as f:
                    raw_list = json.load(f)
            except:
                raw_list = []

        raw_list.append(raw_data)
        with open(raw_file, "w", encoding="utf-8") as f:
            json.dump(raw_list, f, ensure_ascii=False, indent=2)

    def should_alert(self, stock):
        """判断是否应该告警"""
        symbol = stock["symbol"]
        limit_days = stock["limit_up_days"]

        if limit_days < 2:
            return False

        if symbol in self.stock_alerts:
            if limit_days in self.stock_alerts[symbol]:
                return False

        return True

    def check_new_stocks(self, current_data):
        """检查新股票和连板变化"""
        if not current_data:
            return []

        current_symbols = set(stock["symbol"] for stock in current_data)

        if not self.known_stocks:
            self.known_stocks = current_symbols.copy()
            self.log_signal.emit(
                f"初始化监控，当前涨停股票数量: {len(current_symbols)}"
            )
            return []

        alert_stocks = []
        for stock in current_data:
            symbol = stock["symbol"]
            if symbol not in self.known_stocks or self.should_alert(stock):
                alert_stocks.append(stock)

        self.known_stocks = current_symbols.copy()
        return alert_stocks

    def save_alert(self, stock, plate_reason):
        """保存告警记录"""
        alert_file = f"alerts_{self.today}.json"
        alert_data = {
            "timestamp": datetime.now().isoformat(),
            "symbol": stock["symbol"],
            "stock_name": stock["stock_chi_name"],
            "price": stock["price"],
            "change_percent": stock["change_percent"],
            "limit_up_days": stock["limit_up_days"],
            "plate_reason": plate_reason,
        }

        alerts = []
        if os.path.exists(alert_file):
            try:
                with open(alert_file, "r", encoding="utf-8") as f:
                    alerts = json.load(f)
            except:
                alerts = []

        alerts.append(alert_data)
        with open(alert_file, "w", encoding="utf-8") as f:
            json.dump(alerts, f, ensure_ascii=False, indent=2)

        # 更新股票告警历史（保持原有逻辑）
        symbol = stock["symbol"]
        limit_days = stock["limit_up_days"]
        if symbol not in self.stock_alerts:
            self.stock_alerts[symbol] = []
        if limit_days not in self.stock_alerts[symbol]:
            self.stock_alerts[symbol].append(limit_days)

        # 更新板块原因告警历史
        if plate_reason:
            self.reason_alerts[plate_reason] = True

    def alert_stocks(self, stocks):
        """处理告警 - 只显示与关键词匹配的告警，并确保每个关键词只弹窗一次"""
        if not stocks:
            return

        for stock in stocks:
            surge_reason_obj = stock.get("surge_reason")
            if not surge_reason_obj:
                continue  # 跳过当前股票

            related_plates = surge_reason_obj.get("related_plates", [])
            if not related_plates:
                continue  # 跳过没有相关板块原因的股票

            # 提取板块名称
            plate_name = related_plates[0].get("plate_name", "")
            if not plate_name:
                continue  # 如果板块名称为空，则跳过

            # 检查关键词是否在板块名称中
            for keyword in self.surge_keywords:
                if keyword in plate_name:
                    # 如果关键词已经告警过，则跳过
                    if keyword in self.triggered_keywords:
                        return  # 已经触发过告警，不再处理

                    # 记录关键词以避免重复告警
                    self.triggered_keywords[keyword] = True

                    alert_info = {
                        "plate_reason": related_plates[0].get("plate_reason", ""),
                        "stocks": [stock],
                        "count": 1
                    }
                    self.log_signal.emit(f"🚨 告警: {plate_name} (关键词: {keyword})")
                    self.alert_signal.emit(alert_info)

                    # 记录已告警的板块原因
                    if alert_info["plate_reason"] not in self.reason_alerts:
                        self.reason_alerts[alert_info["plate_reason"]] = True

    def calculate_stats(self, current_data):
        """计算统计信息"""
        if not current_data:
            return {}

        total_stocks = len(current_data)
        limit_up_days_count = {}
        total_alerts_today = 0

        # 统计连板天数分布
        for stock in current_data:
            days = stock["limit_up_days"]
            limit_up_days_count[days] = limit_up_days_count.get(days, 0) + 1

        # 统计今日告警数量
        alert_file = f"alerts_{self.today}.json"
        if os.path.exists(alert_file):
            try:
                with open(alert_file, "r", encoding="utf-8") as f:
                    alerts = json.load(f)
                    total_alerts_today = len(alerts)
            except:
                pass

        return {
            "total_stocks": total_stocks,
            "limit_up_days_count": limit_up_days_count,
            "total_alerts_today": total_alerts_today,
            "update_time": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
        }

    def start_monitoring(self):
        """开始监控"""
        self.running = True
        self.log_signal.emit("开始股票涨停板监控...")

    def stop_monitoring(self):
        """停止监控"""
        self.running = False
        self.log_signal.emit("停止股票涨停板监控")

    def run(self):
        """运行监控"""
        while True:
            if not self.running:
                time.sleep(1)
                continue

            try:
                self.check_date_change()
                current_data = self.fetch_data()

                if current_data:
                    # 使用新的告警逻辑
                    alert_stocks = self.get_alert_stocks(current_data)
                    if alert_stocks:
                        self.alert_stocks(alert_stocks)
                    else:
                        self.log_signal.emit(
                            f"当前涨停股票数量: {len(current_data)}, 无新告警"
                        )

                    # 发送统计信息
                    stats = self.calculate_stats(current_data)
                    self.stats_signal.emit(stats)
                else:
                    self.log_signal.emit("获取数据失败")

                self.mutex.lock()
                interval = self.config.get("check_interval", 3)
                self.mutex.unlock()

                time.sleep(interval)

            except Exception as e:
                self.log_signal.emit(f"监控错误: {e}")
                time.sleep(5)


class MainWindow(QMainWindow):
    """主窗口"""

    def __init__(self):
        super().__init__()
        self.alert_windows = []
        self.init_ui()
        self.setup_monitor()
        self.setup_log_timer()

    def init_ui(self):
        """初始化界面"""
        self.setWindowTitle("股票涨停板监控系统")
        self.setGeometry(100, 100, 1200, 800)

        # 创建中心部件
        central_widget = QWidget()
        self.setCentralWidget(central_widget)

        # 创建主布局
        main_layout = QHBoxLayout(central_widget)

        # 创建左侧面板（配置和控制）
        left_panel = self.create_left_panel()

        # 创建右侧面板（日志和统计）
        right_panel = self.create_right_panel()

        # 添加到主布局
        splitter = QSplitter(Qt.Horizontal)
        splitter.addWidget(left_panel)
        splitter.addWidget(right_panel)
        splitter.setSizes([400, 800])

        main_layout.addWidget(splitter)

        # 创建状态栏
        self.statusBar().showMessage("就绪")

        # 设置样式
        self.setStyleSheet(
            """
            QMainWindow {
                background-color: #f5f5f5;
            }
            QGroupBox {
                font-weight: bold;
                border: 2px solid #cccccc;
                border-radius: 5px;
                margin-top: 10px;
                padding-top: 10px;
            }
            QGroupBox::title {
                subcontrol-origin: margin;
                left: 10px;
                padding: 0 5px 0 5px;
            }
            QPushButton {
                background-color: #4CAF50;
                color: white;
                border: none;
                padding: 8px 16px;
                border-radius: 4px;
                font-weight: bold;
            }
            QPushButton:hover {
                background-color: #45a049;
            }
            QPushButton:pressed {
                background-color: #3d8b40;
            }
            QPushButton:disabled {
                background-color: #cccccc;
                color: #666666;
            }
            #stop_button {
                background-color: #f44336;
            }
            #stop_button:hover {
                background-color: #da190b;
            }
            #view_alerts_button {
                background-color: #2196F3;
            }
            #view_alerts_button:hover {
                background-color: #0b7dda;
            }
        """
        )

    def is_trading_time(self):
        """检查当前时间是否在交易时间内（9:25-15:00）"""
        now = datetime.now()
        current_time = now.time()

        # 交易时间：9:25-15:00
        trading_start = datetime.strptime("09:25", "%H:%M").time()
        trading_end = datetime.strptime("15:00", "%H:%M").time()

        # 检查是否为工作日（周一到周五）
        if now.weekday() >= 5:  # 0-4是周一到周五，5-6是周六周日
            return False, "当前为周末，股市不开盘"

        # 检查是否在交易时间内
        if trading_start <= current_time <= trading_end:
            return True, ""
        else:
            if current_time < trading_start:
                return False, f"当前时间 {current_time.strftime('%H:%M')} 早于开盘时间 09:25"
            else:
                return False, f"当前时间 {current_time.strftime('%H:%M')} 晚于收盘时间 15:00"

    def create_left_panel(self):
        """创建左侧面板"""
        left_widget = QWidget()
        left_layout = QVBoxLayout(left_widget)

        # 配置组
        config_group = QGroupBox("配置设置")
        config_layout = QFormLayout(config_group)

        # 代理设置
        # 预设关键词设置
        self.keyword1_edit = QLineEdit()
        self.keyword2_edit = QLineEdit()
        self.keyword3_edit = QLineEdit()
        self.use_proxy_cb = QCheckBox("使用代理")
        self.proxy_host_edit = QLineEdit("127.0.0.1")
        self.proxy_port_edit = QSpinBox()
        self.proxy_port_edit.setRange(1, 65535)
        self.proxy_port_edit.setValue(8442)

        config_layout.addRow("", self.use_proxy_cb)
        config_layout.addRow("代理主机:", self.proxy_host_edit)
        config_layout.addRow("代理端口:", self.proxy_port_edit)
        config_layout.addRow("涨停关键词1:", self.keyword1_edit)
        config_layout.addRow("涨停关键词2:", self.keyword2_edit)
        config_layout.addRow("涨停关键词3:", self.keyword3_edit)
        # 请求设置
        self.check_interval_spin = QSpinBox()
        self.check_interval_spin.setRange(1, 3600)
        self.check_interval_spin.setValue(1)
        self.check_interval_spin.setSuffix(" 秒")

        self.timeout_spin = QSpinBox()
        self.timeout_spin.setRange(1, 60)
        self.timeout_spin.setValue(10)
        self.timeout_spin.setSuffix(" 秒")

        self.max_retries_spin = QSpinBox()
        self.max_retries_spin.setRange(1, 100)
        self.max_retries_spin.setValue(100)

        config_layout.addRow("检查间隔:", self.check_interval_spin)
        config_layout.addRow("超时时间:", self.timeout_spin)
        config_layout.addRow("最大重试:", self.max_retries_spin)

        # 控制按钮组
        control_group = QGroupBox("控制面板")
        control_layout = QVBoxLayout(control_group)

        self.start_button = QPushButton("开始监控")
        self.stop_button = QPushButton("停止监控")
        self.stop_button.setObjectName("stop_button")
        self.view_alerts_button = QPushButton("查看已告警")
        self.view_alerts_button.setObjectName("view_alerts_button")

        self.start_button.clicked.connect(self.start_monitoring)
        self.stop_button.clicked.connect(self.stop_monitoring)
        self.view_alerts_button.clicked.connect(self.view_alerts)

        control_layout.addWidget(self.start_button)
        control_layout.addWidget(self.stop_button)
        control_layout.addWidget(self.view_alerts_button)

        # 统计信息组
        stats_group = QGroupBox("统计信息")
        stats_layout = QFormLayout(stats_group)

        self.total_stocks_label = QLabel("0")
        self.total_alerts_label = QLabel("0")
        self.update_time_label = QLabel("未更新")

        stats_layout.addRow("当前涨停股票数:", self.total_stocks_label)
        stats_layout.addRow("今日告警数:", self.total_alerts_label)
        stats_layout.addRow("最后更新:", self.update_time_label)

        # 连板统计
        self.limit_stats_table = QTableWidget()
        self.limit_stats_table.setColumnCount(2)
        self.limit_stats_table.setHorizontalHeaderLabels(["连板天数", "股票数量"])
        self.limit_stats_table.setMaximumHeight(150)

        stats_layout.addRow("连板分布:", self.limit_stats_table)

        # 添加到左侧布局
        left_layout.addWidget(config_group)
        left_layout.addWidget(control_group)
        left_layout.addWidget(stats_group)
        left_layout.addStretch()

        return left_widget

    def create_right_panel(self):
        """创建右侧面板"""
        right_widget = QWidget()
        right_layout = QVBoxLayout(right_widget)

        # 日志显示
        log_group = QGroupBox("运行日志")
        log_layout = QVBoxLayout(log_group)

        self.log_text = QTextEdit()
        self.log_text.setReadOnly(True)
        self.log_text.setFont(QFont("Consolas", 9))

        # 日志控制按钮
        log_control_layout = QHBoxLayout()
        self.clear_log_button = QPushButton("清空日志")
        self.save_log_button = QPushButton("保存日志")

        self.clear_log_button.clicked.connect(self.clear_logs)
        self.save_log_button.clicked.connect(self.save_logs)

        log_control_layout.addWidget(self.clear_log_button)
        log_control_layout.addWidget(self.save_log_button)
        log_control_layout.addStretch()

        log_layout.addWidget(self.log_text)
        log_layout.addLayout(log_control_layout)

        right_layout.addWidget(log_group)

        return right_widget

    def setup_monitor(self):
        """设置监控"""
        # 初始化配置
        self.config = {
            "use_proxy": False,
            "proxy_host": "127.0.0.1",
            "proxy_port": 8442,
            "check_interval": 60,
            "timeout": 10,
            "max_retries": 5,
        }

        # 创建工作线程
        self.worker = StockMonitorWorker(self.config)
        self.worker_thread = QThread()
        self.worker.moveToThread(self.worker_thread)

        # 连接信号
        self.worker.alert_signal.connect(self.show_alert)
        self.worker.log_signal.connect(self.add_log)
        self.worker.stats_signal.connect(self.update_stats)

        # 启动线程
        self.worker_thread.started.connect(self.worker.run)
        self.worker_thread.start()

        # 初始状态
        self.stop_button.setEnabled(False)

    def setup_log_timer(self):
        """设置日志定时器"""
        self.log_timer = QTimer()
        self.log_timer.timeout.connect(self.update_log_display)
        self.log_timer.start(1000)  # 每秒更新一次

    def get_config(self):
        """获取当前配置"""
        return {
            "keyword1": self.keyword1_edit.text(),
            "keyword2": self.keyword2_edit.text(),
            "keyword3": self.keyword3_edit.text(),
            "use_proxy": self.use_proxy_cb.isChecked(),
            "proxy_host": self.proxy_host_edit.text(),
            "proxy_port": self.proxy_port_edit.value(),
            "check_interval": self.check_interval_spin.value(),
            "timeout": self.timeout_spin.value(),
            "max_retries": self.max_retries_spin.value(),
        }

    def start_monitoring(self):
        """开始监控"""
        # 检查交易时间
        is_trading, time_message = self.is_trading_time()

        if not is_trading:
            # 弹出提示框
            msg_box = QMessageBox(self)
            msg_box.setWindowTitle("交易时间提醒")
            msg_box.setIcon(QMessageBox.Warning)
            msg_box.setText("当前不在股市交易时间内")
            msg_box.setInformativeText(f"{time_message}\n\n股市交易时间：周一至周五 09:25-15:00")
            msg_box.setStandardButtons(QMessageBox.Ok | QMessageBox.Ignore)
            msg_box.setDefaultButton(QMessageBox.Ok)

            # 设置按钮文本
            ok_button = msg_box.button(QMessageBox.Ok)
            ok_button.setText("确定")
            ignore_button = msg_box.button(QMessageBox.Ignore)
            ignore_button.setText("忽略继续")

            result = msg_box.exec_()

            # 如果用户选择确定，则不启动监控
            if result == QMessageBox.Ok:
                self.add_log(f"监控启动被取消：{time_message}")
                return
            else:
                self.add_log(f"忽略交易时间限制，强制启动监控：{time_message}")

        """开始监控"""
        self.config = self.get_config()
        self.worker.update_config(self.config)
        self.worker.start_monitoring()

        self.start_button.setEnabled(False)
        self.stop_button.setEnabled(True)
        self.statusBar().showMessage("监控运行中...")

    def stop_monitoring(self):
        """停止监控"""
        self.worker.stop_monitoring()

        self.start_button.setEnabled(True)
        self.stop_button.setEnabled(False)
        self.statusBar().showMessage("监控已停止")

    def show_alert(self, alert_info):
        """显示告警弹窗"""
        alert_window = AlertWindow(alert_info)
        alert_window.show()
        self.alert_windows.append(alert_window)

        # 清理已关闭的窗口
        self.alert_windows = [w for w in self.alert_windows if w.isVisible()]

    def add_log(self, message):
        """添加日志"""
        timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
        log_message = f"[{timestamp}] {message}"

        self.log_text.append(log_message)

        # 保持最多1000行日志
        cursor = self.log_text.textCursor()
        cursor.movePosition(QTextCursor.Start)
        text = self.log_text.toPlainText()
        lines = text.split("\n")
        if len(lines) > 1000:
            lines = lines[-1000:]
            self.log_text.setPlainText("\n".join(lines))

        # 滚动到底部
        cursor.movePosition(QTextCursor.End)
        self.log_text.setTextCursor(cursor)

    def update_log_display(self):
        """更新日志显示"""
        pass  # 现在日志直接通过信号更新

    def update_stats(self, stats):
        """更新统计信息"""
        self.total_stocks_label.setText(str(stats.get("total_stocks", 0)))
        self.total_alerts_label.setText(str(stats.get("total_alerts_today", 0)))
        self.update_time_label.setText(stats.get("update_time", "未更新"))

        # 更新连板统计表
        limit_up_days_count = stats.get("limit_up_days_count", {})
        self.limit_stats_table.setRowCount(len(limit_up_days_count))

        for i, (days, count) in enumerate(sorted(limit_up_days_count.items())):
            self.limit_stats_table.setItem(i, 0, QTableWidgetItem(f"{days}板"))
            self.limit_stats_table.setItem(i, 1, QTableWidgetItem(str(count)))

    def view_alerts(self):
        """查看已告警"""
        today = datetime.now().strftime("%Y-%m-%d")
        alert_file = f"alerts_{today}.json"

        if os.path.exists(alert_file):
            try:
                # 尝试用默认程序打开
                if sys.platform == "win32":
                    os.startfile(alert_file)
                elif sys.platform == "darwin":
                    subprocess.run(["open", alert_file])
                else:
                    subprocess.run(["xdg-open", alert_file])

                self.add_log(f"已打开告警文件: {alert_file}")
            except Exception as e:
                QMessageBox.warning(self, "警告", f"无法打开告警文件: {e}")
        else:
            QMessageBox.information(
                self, "信息", f"今日暂无告警记录\n文件: {alert_file}"
            )

    def clear_logs(self):
        """清空日志"""
        self.log_text.clear()
        self.add_log("日志已清空")

    def save_logs(self):
        """保存日志"""
        filename, _ = QFileDialog.getSaveFileName(
            self,
            "保存日志",
            f"monitor_log_{datetime.now().strftime('%Y%m%d_%H%M%S')}.txt",
            "Text Files (*.txt)",
        )

        if filename:
            try:
                with open(filename, "w", encoding="utf-8") as f:
                    f.write(self.log_text.toPlainText())
                self.add_log(f"日志已保存到: {filename}")
            except Exception as e:
                QMessageBox.warning(self, "错误", f"保存日志失败: {e}")

    def closeEvent(self, event):
        """关闭事件"""
        # 停止监控
        self.worker.stop_monitoring()

        # 关闭所有告警窗口
        for window in self.alert_windows:
            window.close()

        # 等待线程结束
        self.worker_thread.quit()
        self.worker_thread.wait(3000)  # 等待3秒

        event.accept()


def main():
    """主函数"""
    app = QApplication(sys.argv)
    app.setApplicationName("股票涨停板监控系统")
    app.setApplicationVersion("1.0.0")

    # 创建主窗口
    window = MainWindow()
    window.show()

    # 运行应用
    sys.exit(app.exec_())


if __name__ == "__main__":
    main()
